package com.springboot.demo.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.springboot.demo.common.CustomFilter;
import com.springboot.demo.common.CustomUrlDecisionManager;
import com.springboot.demo.common.MyUsernamePasswordAuthenticationFilter;
import com.springboot.demo.entity.SystemLog;
import com.springboot.demo.entity.User;
import com.springboot.demo.entity.UserDetailsImpl;
import com.springboot.demo.mapper.UserMapper;
import com.springboot.demo.service.UserService;
import com.springboot.demo.utils.DateUtils;
import com.springboot.demo.utils.RedisUtils;
import com.springboot.demo.utils.ResponseResult;
import com.springboot.demo.utils.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import springfox.documentation.spring.web.json.Json;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;


/**
 * @author wubc683
 * @desc
 * @date 2021-07-26 11:02
 */

@Configuration
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    UserService userService;
    @Autowired
    UserDetailsService userDetailsService;
    @Autowired
    DataSource dataSource;
    @Autowired
    PersistentTokenRepository repository;
    @Autowired
    SessionExpiredStrategy sessionExpiredStrategy;
    @Autowired
    CustomUrlDecisionManager customUrlDecisionManager;
    @Autowired
    CustomFilter customFilter;
    @Autowired
    ResponseUtils responseUtils;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    UserMapper userMapper;
//    @Autowired
//    CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/", "/favicon.ico", "/js/**", "/css/**", "/login", "/common/**",
                "/session/invalid",
                "/test/**", "/myResources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
//                .contentTypeOptions().disable()
                .and()
                .cors().and()
                .authorizeRequests()
//                 动态权限配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(customUrlDecisionManager);
                        object.setSecurityMetadataSource(customFilter);
                        return object;
                    }
                })
                .antMatchers(HttpMethod.OPTIONS).permitAll()
//                .anyRequest()
//                .authenticated()
//                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll() // 处理跨域请求中的Preflight请求
                .and()
                .formLogin()
                .loginPage("/login")                                //登陆页面
                .loginProcessingUrl("/doLogin")                  //登陆请求处理接口
//                .successForwardUrl("/index")
//                .usernameParameter("admin")                       //默认用户名，密码
//                .passwordParameter("admin")
                .permitAll()
                .successHandler(new AuthenticationSuccessHandler() {            //登陆成功后
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req,
                                                        HttpServletResponse resp,
                                                        Authentication auth)    //当前用户登陆信息
                            throws IOException {
                        Object principal = auth.getPrincipal();
                        UserDetailsImpl userDetailsImpl = (UserDetailsImpl) principal;
                        User implUser = userDetailsImpl.getUser();
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        resp.setStatus(200);
                        JSONObject object = new JSONObject();
                        object.put("userId", implUser.getId());
                        ResponseUtils r = responseUtils.buildSuccessResponse(object);
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(r));
                        out.flush();
                        out.close();
                        log.info("request url :{}", req.getRequestURL());
                        // 保存系统登录日志
                        Long uid = implUser.getId();
                        SystemLog systemLog = new SystemLog();
                        systemLog.setUsername(implUser.getUsername());
                        systemLog.setBrowser(req.getHeader("User-Agent"));
                        systemLog.setIp(req.getRemoteAddr());
                        systemLog.setOperator("login");
                        systemLog.setLoginTime(new Date());
                        userService.saveSystemLog(uid, systemLog);
//                        RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
//                         重定向
//                        redirectStrategy.sendRedirect(req, resp, "/index");
                    }
                })
                .failureHandler(new AuthenticationFailureHandler() {         //登陆失败后
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req,
                                                        HttpServletResponse resp,
                                                        AuthenticationException e)  //获取登陆失败原因
                            throws IOException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        resp.setStatus(401);
                        String message = null;
                        if (e instanceof LockedException) {
                            message = "账户被锁定，登录失败!";
                        } else if (e instanceof BadCredentialsException) {
                            message = "账户名或密码输入错误，登录失败!";
                        } else if (e instanceof DisabledException) {
                            message = "账户未激活，登录失败!";
                        } else if (e instanceof AccountExpiredException) {
                            message = "账户已过期，登录失败!";
                        } else if (e instanceof CredentialsExpiredException) {
                            message = "密码已过期，登录失败!";
                        } else {
                            message = "用户名不存在,登录失败!";
                        }
                        ResponseUtils r = responseUtils.buildFailedResponse(401, message);
                        ObjectMapper om = new ObjectMapper();
                        out.write(om.writeValueAsString(r));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                //异常处理（权限失效，登录失效等）
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        response.setContentType("text/json;charset=utf-8");
                        ObjectMapper om = new ObjectMapper();
                        PrintWriter out = response.getWriter();
                        out.write(om.writeValueAsString(responseUtils.buildFailedResponse(ResponseResult.USER_NOT_LOGIN)));
                        out.flush();
                        out.close();
                    }
                })
                .and()
                .rememberMe()                                            //记住我功能开启
                .rememberMeParameter("remember-me")
                .tokenRepository(repository)
//                .tokenValiditySeconds(60)         //记住我的时间--token的过期时间,单位：秒
                .userDetailsService(userDetailsService)                     //最终拿到用户名后用userDetailsService去登入
                .and()
                .sessionManagement() // 添加 Session管理器
//                .invalidSessionUrl("/session/invalid") // Session失效后跳转到这个链接
                .maximumSessions(1)
                .expiredSessionStrategy(sessionExpiredStrategy)
//                .expiredUrl("/session/expired")
                .and()
                .and()
                .logout()                   //开启注销登陆
                .logoutUrl("/logout")       //注销登陆请求url
                .clearAuthentication(true)  //清除身份信息
                .invalidateHttpSession(true)//session失效
                .addLogoutHandler(new LogoutHandler() {//注销处理
                    @Override
                    public void logout(HttpServletRequest req,
                                       HttpServletResponse resp,
                                       Authentication auth) {
                        Object principal = auth.getPrincipal();
                        UserDetailsImpl userDetailsImpl = (UserDetailsImpl) principal;
                        User implUser = userDetailsImpl.getUser();
                        String[] strings = null;
                        if (implUser.getParentId() == 0) {
                            List<Long> relativeUserIdByAdminId = userMapper.getRelativeUserIdByAdminId(implUser.getId());
                            strings = new String[1 + relativeUserIdByAdminId.size()];
                            strings[0] = "user::" + implUser.getId();
                            for (int i = 1; i < strings.length; i++) {
                                strings[i] = "relativeUser::" + relativeUserIdByAdminId.get(i - 1);
                            }
                        } else {
                            strings = new String[1];
                            strings[0] = "user::" + implUser.getId();
                        }
                        redisUtils.delete(strings);
                    }
                })
                .logoutSuccessHandler(new LogoutSuccessHandler() {     //注销成功处理
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req,
                                                HttpServletResponse resp,
                                                Authentication auth)
                            throws IOException {
                        resp.setContentType("text/json;charset=utf-8");
                        ObjectMapper om = new ObjectMapper();
                        PrintWriter out = resp.getWriter();
                        out.write(om.writeValueAsString(responseUtils.buildSuccessResponse()));
                        out.flush();
                        out.close();
                    }
                })
                .and();

    }

    /**
     * remember-me功能默认配置类，用于将用户登录产生的token数据存入数据库，用户下次登录不需要输入账号密码
     *
     * @param
     * @return: org.springframework.security.web.authentication.rememberme.PersistentTokenRepository
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();//这个接口完成建表，查询，修改等操作
        tokenRepository.setDataSource(dataSource);
        //启动时创建一张表，这个参数到第二次启动时必须注释掉，因为已经创建了一张表
//        tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOriginPattern("*");
        config.setAllowCredentials(true);
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        source.registerCorsConfiguration("/**", config);

        CorsFilter bean = new CorsFilter(source);
        System.out.println("filterregbean run");
        return bean;
    }

    @Bean
    public CookieSerializer httpSessionIdResolver() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        // 取消仅限同一站点设置
        cookieSerializer.setSameSite(null);
        return cookieSerializer;
    }

//    @Bean
//    MyUsernamePasswordAuthenticationFilter myAuthenticationFilter() throws Exception {
//        MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();
//        filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
//            @Override
//            public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
//                resp.setContentType("application/json;charset=utf-8");
//                PrintWriter out = resp.getWriter();
//                Map<String, Object> map = new HashMap<>();
//                map.put("status", 200);
//                map.put("msg", authentication.getPrincipal());
//                out.write(new ObjectMapper().writeValueAsString(map));
//                out.flush();
//                out.close();
//            }
//        });
//        filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
//            @Override
//            public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
//                resp.setContentType("application/json;charset=utf-8");
//                PrintWriter out = resp.getWriter();
//                Map<String, Object> map = new HashMap<>();
//                map.put("status", 401);
//                if (e instanceof LockedException) {
//                    map.put("msg", "账户被锁定，登录失败!");
//                } else if (e instanceof BadCredentialsException) {
//                    map.put("msg", "用户名或密码输入错误，登录失败!");
//                } else if (e instanceof DisabledException) {
//                    map.put("msg", "账户被禁用，登录失败!");
//                } else if (e instanceof AccountExpiredException) {
//                    map.put("msg", "账户过期，登录失败!");
//                } else if (e instanceof CredentialsExpiredException) {
//                    map.put("msg", "密码过期，登录失败!");
//                } else {
//                    map.put("msg", "登录失败!");
//                }
//                out.write(new ObjectMapper().writeValueAsString(map));
//                out.flush();
//                out.close();
//            }
//        });
//        filter.setAuthenticationManager(authenticationManagerBean());
//        return filter;
//    }

//    @Bean
//    CorsConfigurationSource corsConfigurationSource(){
//        CorsConfiguration corsConfiguration = new CorsConfiguration();
//        //允许站点跨域
//        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
//
//        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
//        //允许携带凭证
//        corsConfiguration.setAllowCredentials(true);
//        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//        //对所有URL生效
//        source.registerCorsConfiguration("/**",corsConfiguration);
//        System.out.println("cors runnnnn");
//        return source;
//    }
}